/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2007 OPeNDAP, Inc.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
/////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using NDODS.DAP.BaseTypes;
using NDODS.DAP.Exceptions;
using NDODS.Util;
//UPGRADE_TODO: The package 'org.jdom' could not be found. If it was not included in the conversion, there may be compiler issues. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1262'"
//using org.jdom;
//UPGRADE_TODO: The type 'org.jdom.input.SAXBuilder' could not be found. If it was not included in the conversion, there may be compiler issues. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1262'"
//using SAXBuilder = org.jdom.input.SAXBuilder;
//using Debug = opendap.util.Debug;

namespace NDODS.DAP.XMLParser
{
    /// <summary> <code>DDSXMLParser</code> is used to parse a DDX (the XML formatted persistent
    /// representation of a DDS) into a DDS object in memory. This parser uses the
    /// Xerces XML parser and JDOM packages to perform it's duties.
    /// 
    /// </summary>
    /// <author>  ndp
    /// </author>
    /// <version>  $Revision: 15901 $
    /// </version>
    /// <opendap.ddx.experimental>  WARNING! This class supports a pre-released version of the DDX. </opendap.ddx.experimental>
    /// <summary> The XML schema that this class is designed to work with WILL BE CHANGED prior
    /// to software release.
    /// </summary>
    public class DDSXMLParser
    {
        /// <summary> Returns the <code>Document</code> object created by the most
        /// recent invocation of the parser. This might be usefull to some XML
        /// hacker somewhere. :)
        /// 
        /// </summary>
        /// <returns> The <code>Document</code> object created by the most
        /// recent invocation of the parser.
        /// </returns>
        public virtual XmlDocument LastDomDoc
        {
            get { return (lastDoc); }
        }

        // Debugging output control
        private bool _Debug = false;

        // depth of recursion in the parser.
        // This used only for Debugging purposes.
        private int parseLevel;

        // The DDS that the parser is building.
        private DDS dds = null;

        // The BaseTypeFactory the parser uses
        // to create each variable in the DDS.
        private IBaseTypeFactory factory = null;

        // The namespace through which the XML document is validated.
        // This gets set up in the constructor for this class.
        private XmlNamespaceManager opendapNameSpace;
        private string strOpendapNameSpace;


        // The parent DConstructor of the current variable.
        // This is state information used by the parser.
        // Variables in the DDS are always added to a
        // a container of this type (DDS is an instance
        // of a DConstructor)
        private DConstructor parentDC;

        // The BaseType thatis currently being parsed.
        // This is state information used by the parser.
        private BaseType currentBT = null;

        // The Attribute Table (container) to which
        // Attributes currently being parsed should be added.
        // This is state information used by the parser.
        private AttributeTable currentAT = null;

        // The last DOM Document that we used to construct a DDS;
        private XmlDocument lastDoc;

        /// <summary> Constructs a new <code>DDSXMLParser</code>.
        /// The OPeNDAP namespace is defined during the construction
        /// of an instance of this class.
        /// </summary>
        public DDSXMLParser(String nameSpace)
        {
            strOpendapNameSpace = nameSpace;
            lastDoc = null;
            _Debug = Debug.isSet("DDSXMLParser");
        }


        /// <summary> Parse the DDX waiting in the <code>InputStream</code> and instantiate all of
        /// the member <code>BaseType</code> variables and their associated <code>Attributes
        /// </code> into a <code>DDS</code> using the passed <code>BaseTypeFactory</code>
        /// 
        /// </summary>
        /// <param name="ddx">       A JDOM <code>Document</code> containing the DDX to parse.
        /// </param>
        /// <param name="targetDDS"> The <code>DDS</code> in which to place all of the <code>BaseType
        /// </code> variables and their associated <code>Attributes</code>.
        /// </param>
        /// <param name="fac">       The <code>BaseTypeFactory</code> to use when creating new
        /// <code>BaseType</code> variables.
        /// </param>
        /// <param name="validation">Is a bool indicating whether or not the parser should
        /// validate the XML document using the Schema (typically referenced in the
        /// document itself). In general server side applications should always vaidate,
        /// while clients shouldn't bother (since they are ostensibly receiving the
        /// document from a server that has already done so.)
        /// </param>
        /// <seealso cref="DDS">
        /// </seealso>
        /// <seealso cref="IBaseTypeFactory">
        /// </seealso>
        public virtual void parse(XmlDocument ddx, DDS targetDDS, IBaseTypeFactory fac, bool validation)
        {
            dds = targetDDS;
            factory = fac;

            // Build up the OPeNDAP data objects rpresented by the XML document.
            // Additional validation will take place  during this process.
            opendapNameSpace = new XmlNamespaceManager(ddx.NameTable);
            opendapNameSpace.AddNamespace("", strOpendapNameSpace);

            XmlElement root = ddx.DocumentElement;
            lastDoc = ddx;

            // This is just a little tracker to help with debugging.
            parseLevel = 0;

            // Make sure the root element is in fact a Dataset.
            // Trying to enforce this in the schema would create a
            // contrived and difficult to interpret schema design.
            String type = root.Name;
            if (!(type.Equals("Dataset")))
            {
                throw new NoSuchTypeException("Root Element MUST be <Dataset>. Found: " + type);
            }

            String name = root.GetAttribute("name");
            //System.out.println("DDS should be named: "+name);
            dds.ClearName = name;
            parentDC = dds;
            currentBT = dds;

            // Parse any Attributes (or AttributeTables/containers) at the
            // top level in the Dataset.
            parseAttributes(root, "-- ");

            // Parse any Aliases at the
            // top level in the Dataset.
            parseAliases(root, "++ ");

            // Parse all of the child elements (which would be OPeNDAP
            // BaseType variables) in the Dataset.
            IEnumerator ci = root.ChildNodes.GetEnumerator();
            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            while (ci.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                XmlElement child = (XmlElement) ci.Current;
                parseBase(child, "    ");
            }

            //	catch(Exception e) {
            //	    throw new DAP2Exception("PARSER ERROR! \n"+
            //	                            e.getClass().Name+ ": "+e.getMessage());
            //	}
        }

        /// <summary> Parse the DDX waiting in the <code>InputStream</code> and instantiate all of
        /// the member <code>BaseType</code> variables and their associated <code>Attributes
        /// </code> into a <code>DDS</code> using the passed <code>BaseTypeFactory</code>
        /// 
        /// </summary>
        /// <param name="is">        The <code>InputStream</code> containing the DDX to parse.
        /// </param>
        /// <param name="targetDDS"> The <code>DDS</code> in which to place all of the <code>BaseType
        /// </code> variables and their associated <code>Attributes</code>.
        /// </param>
        /// <param name="fac">       The <code>BaseTypeFactory</code> to use when creating new
        /// <code>BaseType</code> variables.
        /// </param>
        /// <param name="validation">Is a bool indicating whether or not the parser should
        /// validate the XML document using the Schema (typically referenced in the
        /// document itself). In general server side applications should always vaidate,
        /// while clients shouldn't bother (since they are ostensibly receiving the
        /// document from a server that has already done so.)
        /// </param>
        /// <seealso cref="DDS">
        /// </seealso>
        /// <seealso cref="IBaseTypeFactory">
        /// </seealso>
        public virtual void parse(Stream is_Renamed, DDS targetDDS, IBaseTypeFactory fac, bool validation)
        {
            try
            {
                // get a jdom parser to parse and validate the XML document.
                XmlReaderSettings parserSettings = new XmlReaderSettings();

                // turn on validation
                parserSettings.Schemas.Add(null, "http://apache.org/xml/features/validation/schema");
                parserSettings.ValidationType = ValidationType.Schema;

                XmlReader parser = XmlReader.Create(is_Renamed, parserSettings);

                // parse the document into a hierarchical document
                XmlDocument doc = new XmlDocument();
                doc.Load(parser);

                ValidationEventHandler eventHandler = new ValidationEventHandler(ValidationEventHandler);
                doc.Validate(eventHandler);

                if (_Debug)
                    Console.Out.WriteLine("Document is " + (validation ? "valid and " : "") + "well-formed.\nContent: " +
                                          doc);

                parse(doc, targetDDS, fac, validation);
            }
            catch (XmlException jde)
            {
                throw new DDSException(DAP2Exception.UNKNOWN_ERROR, jde.Message);
            }
            catch (IOException ioe)
            {
                //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.getMessage' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                throw new DDSException(DAP2Exception.UNKNOWN_ERROR, ioe.Message);
            }
        }


        static void ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            switch (e.Severity)
            {
                case XmlSeverityType.Error:
                    Console.WriteLine("Error: {0}", e.Message);
                    break;
                case XmlSeverityType.Warning:
                    Console.WriteLine("Warning {0}", e.Message);
                    break;
            }

        }


        /// <summary> This method recursively travels through the DOM tree, locating
        /// BaseType derived nodes and placing them in the DDS. The structure
        /// of the BaseType derived elements in the XML instance document
        /// is captured in the DOM object that is being parsed. This structure
        /// again reflected in the resulting DDS.
        /// </summary>
        private void parseBase(XmlElement e, String indent)
        {
            parseLevel++;

            String type = e.Name;

            if (type.Equals("Attribute"))
            {
                // Do nothing here, the Attributes get parsed when the BaseType's
                // get built. This conditional basically serves as a "trap" to
                // ignore the <Attribute> tag.
            }
            else if (type.Equals("Alias"))
            {
                // Do nothing here, the Aliases get parsed when the BaseType's
                // get built. This conditional basically serves as a "trap" to
                // ignore the <Alias> tag.
            }
            else if (type.Equals("dataBLOB"))
            {
                // dataBLOB?
                // The schema says that the href attribute is
                // required for the dataBLOB element.
                String contentID = e.GetAttribute("href");

                // Since it's required we know that the GetAttribute()
                // method is not going to return null.


                if (_Debug)
                    Console.Out.WriteLine("Found dataBLOB element. contentID=\"" + contentID + "\"");

                dds.BlobContentID = contentID;
            }
            else
            {
                // What's left must be a OPeNDAP BaseType

                if (_Debug)
                    Console.Out.WriteLine("Parsing new BaseType element. Parse level: " + parseLevel);
                if (_Debug)
                    showXMLElement(e, indent);

                // Go get a new BaseType formed from this element
                BaseType bt = newBaseType(e);

                // Set it's parent.
                // bt.setParent(parentDC);

                // Add it to it's parent (container)
                parentDC.addVariable(bt);

                // Now we need to make sure this particular BaseType
                // derived element isn't some special type that needs
                // additional parsing:

                // Is it a container?
                if (bt is DConstructor)
                {
                    // Up date the parsers state, (cache my parent)
                    DConstructor myParentDC = parentDC;
                    parentDC = (DConstructor) bt;

                    // Grids are special containers, handle them
                    if (bt is DGrid)
                    {
                        parseGrid(e, indent);
                    }
                    else
                    {
                        // Otherwise, recurse on the children
                        IEnumerator ci = e.ChildNodes.GetEnumerator();
                        //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                        while (ci.MoveNext())
                        {
                            //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                            XmlElement child = (XmlElement) ci.Current;
                            parseBase(child, indent + "    ");
                        }
                    }
                    // restore my parent
                    parentDC = myParentDC;
                }
                else if (bt is DArray)
                {
                    // Array's are special, better build it if it is one

                    if (_Debug)
                        Console.Out.WriteLine("Parsing Array instance.  Array name: '" + bt.ClearName + "'");

                    parseArray(e, (DArray) bt, indent);
                }
            }

            parseLevel--;
        }


        /// <summary> Arrays have special parsing need as their syntax is different from
        /// that of a typical BaseType derived type or a container type. The
        /// array is based on a template variable that can have ANY structure
        /// that can be represented by the OPeNDAP data model. With the exception
        /// of (you knew this was comming, right?) of other arrays. IE You can't
        /// have Arrays of Arrays. This caveat is enforced by the XML schema.
        /// </summary>
        private void parseArray(XmlElement ArrayElement, DArray da, String indent)
        {
            int countTemplateVars = 0;
            int numDims = 0;


            IEnumerator li = ArrayElement.ChildNodes.GetEnumerator();

            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            while (li.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                XmlElement e = (XmlElement) li.Current;

                if (_Debug)
                    Console.Out.WriteLine(indent + "Working on Array element: " + e.Name);

                // Is this element an Attribute of the Array?
                if (e.Name.Equals("Attribute"))
                {
                    //Then ignore it!
                }
                    // Is this element an Attribute of the Alias?
                else if (e.Name.Equals("Alias"))
                {
                    //Then ignore it!
                }
                    // Is this element an array dimension?
                else if (e.Name.Equals("dimension"))
                {
                    // Then count it,
                    numDims++;

                    // And now let's add it to the array...

                    // Array dimension are not required to have names, so
                    // the schema does not enforce the use of the name attribute.

                    // try to get the dimension's name, and use it id it's there.
                    String name = null;
                    XmlAttribute nameAttr = e.Attributes["name"];

                    if (nameAttr != null)
                        name = nameAttr.Value;

                    // The presence of the 'size' attribute is enforeced by the schema.
                    // get it, parse it, use it.
                    int size = Convert.ToInt32(e.Attributes["size"].Value);

                    // add the dimension to the array.
                    da.appendDim(size, name, false);
                }
                else
                {
                    // otherwise, it must be THE template element.

                    // Just to make sure the schema validation didn't fail (because
                    // I am basically paranoid about software) count the number of
                    // template candidates we find and throw an Exception later
                    // if there was more than one.
                    countTemplateVars++;

                    // The template element is just another BaseType
                    // derived element. So, let's go build it!
                    BaseType template = buildArrayTemplate(e, indent);

                    // Oddly, in the OPeNDAP implmentation of Array, the Array variable
                    // takes it's name from it's (internal) template variable. This
                    // is probably an artifact of the original DDSParser.

                    // So, set the name of the template variable to the name of the Array.
                    template.ClearName = da.ClearName;

                    // Add the template variable to the Array
                    da.addVariable(template);
                }
            }

            if (_Debug)
            {
                Console.Out.WriteLine(indent + "Built Array: ");
                da.PrintDeclaration(Console.OpenStandardOutput(), indent);
                Console.Out.WriteLine(indent + "dimensions: " + numDims + "  templates: " + countTemplateVars);
            }


            if (countTemplateVars != 1)
            {
                throw new NoSuchTypeException("ONLY ONE (1) TEMPLATE VARIABLE ALLOWED PER ARRAY!");
            }
        }


        /// <summary> Builds the template variable for an array. The logic here has a lot
        /// in common with the logic in parseBase. I considered trying to
        /// refactor the code so that the two methods could utilize the same logic,
        /// but I bagged it. Cie' la vie.
        /// <p/>
        /// Arrays of arrays are not allowed this rule should is enforced
        /// through the schema validation process.
        /// </summary>
        private BaseType buildArrayTemplate(XmlElement template, String indent)
        {
            BaseType bt = null;

            if (_Debug)
                showXMLElement(template, indent + "...:");

            // Get all of the Attribute elements (tagged <Attribute>)
            IEnumerator attrElements = template.SelectNodes("Attribute", opendapNameSpace).GetEnumerator();

            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            if (attrElements.MoveNext())
                throw new BadSemanticsException("Array Template Variables MAY NOT have Attributes");

            // Build the appropriate BaseType from the tag.
            bt = newBaseType(template);

            if (_Debug)
                Console.Out.WriteLine("Got template: " + bt.TypeName + "   " + bt.ClearName);

            // Now we need to make sure this particular BaseType
            // derived element isn't some special type that needs
            // additional parsing:

            // Is it a container?
            if (bt is DConstructor)
            {
                // Up date the parsers state, (cache my parent)
                DConstructor myParentDC = parentDC;
                parentDC = (DConstructor) bt;

                // Grids are special containers, handle them
                if (bt is DGrid)
                {
                    parseGrid(template, indent);
                }
                else
                {
                    // Otherwise, recurse on the children
                    IEnumerator cii = template.ChildNodes.GetEnumerator();
                    //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                    while (cii.MoveNext())
                    {
                        //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                        XmlElement child = (XmlElement) cii.Current;
                        parseBase(child, indent + "    ");
                    }
                }
                // restore my parent
                parentDC = myParentDC;
            }

            return (bt);
        }


        /// <summary> Grids are unusual examples of DConstructor and require special
        /// handling when parsing.
        /// </summary>
        private void parseGrid(XmlElement gridElement, String indent)
        {
            parseLevel++;

            // Grab the parent object (which better be a Grid!)
            // just to elminate the hassle of casting everytime...
            DGrid myGrid = (DGrid) parentDC;

            if (_Debug)
            {
                Console.Out.WriteLine("Parsing Grid Element: " + gridElement);
                Console.Out.WriteLine("Grid Elements: ");
                //showXMLElement(gridElement, indent);
                IEnumerator kids = gridElement.ChildNodes.GetEnumerator();
                //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                while (kids.MoveNext())
                {
                    //UPGRADE_TODO: Method 'java.io.PrintStream.println' was converted to 'System.Console.Out.WriteLine' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaioPrintStreamprintln_javalangObject'"
                    //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Object.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                    //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                    Console.Out.WriteLine(kids.Current);
                }
            }

            // Get and parse the grid's Array element.
            String eName = "Array";
            if (_Debug)
            {
                Console.Out.WriteLine("Parsing Array element.");
                Console.Out.WriteLine("Asking for element: '" + eName + "' in namespace: '" + opendapNameSpace + "'");
            }
            XmlElement arrayElement = (XmlElement) gridElement.SelectSingleNode(eName, opendapNameSpace);
            if (_Debug)
                Console.Out.WriteLine("Got Array element: " + arrayElement);
            DArray gridArray = (DArray) newBaseType(arrayElement);
            parseArray(arrayElement, gridArray, indent + "    ");

            // Add it to the Grid
            myGrid.addVariable(gridArray, DGrid.ARRAY);

            // Get the Map elements
            eName = "Map";
            if (_Debug)
            {
                Console.Out.WriteLine("Parsing Map elements.");
                Console.Out.WriteLine("Asking for element: '" + eName + "' in namespace: '" + opendapNameSpace + "'");
            }
            XmlNodeList mapElements = gridElement.SelectNodes("Map", opendapNameSpace);

            // Make sure the number of Map elements matches the dimension of the Grid Array.
            if (mapElements.Count != gridArray.numDimensions())
                throw new BadSemanticsException("Error in Grid syntax: " + "The number of Map arrays must " +
                                                "equal the number of dimensions " + "of the data array.");

            // Parse each Map element and poke it into the Grid.
            for (int i = 0; i < mapElements.Count; i++)
            {
                XmlElement mapElement = (XmlElement) mapElements[i];

                DArray thisMap = (DArray) newBaseType(mapElement);
                parseArray(mapElement, thisMap, indent + "    ");

                if (thisMap.numDimensions() != 1)
                    throw new BadSemanticsException("Error in Grid syntax: " + "Maps may have only one dimension.");

                myGrid.addVariable(thisMap, DGrid.MAPS);
            }

            parseLevel--;
        }

        /// <summary> A convienience function used for displaying information in _Debug mode.
        /// Prints an XML element's name, content (if any) and Attributes to
        /// System.out
        /// </summary>
        private void showXMLElement(XmlElement e, String indent)
        {
            Console.Out.Write(parseLevel + indent + "Element: " + e.Name + "  ");
            String text = e.OuterXml;
            if (!text.Equals(""))
                Console.Out.Write(" = " + text + "   ");

            //System.out.println("");


            IEnumerator atts = e.Attributes.GetEnumerator();
            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            while (atts.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                XmlAttribute att = (XmlAttribute) atts.Current;
                //System.out.Print(parseLevel + indent + "    ");
                Console.Out.Write(att.Name + ": " + att.Value + "  ");
            }
            Console.Out.WriteLine("");

            IEnumerator kids = e.ChildNodes.GetEnumerator();
            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            while (kids.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                XmlElement kid = (XmlElement) kids.Current;
                showXMLElement(kid, indent + "    ");
            }
        }


        /*
		* Builds a new BaseType derived type from the passed XML element.
		* <p>This happens in 4 steps:
		* <ul>
		* <li> 1) Determine the OPeNDAP type and vairiable name </li>
		* <li> 2) Get an new one of the thing in (1) </li>
		* <li> 3) Parse any Attribute tags associated with this OPeNDAP type.
		*         (They appear as children of the XML element)</li>
		* <li> 4) Parse any Alias tags associated with this OPeNDAP type.
		*         (They appear as children of the XML element)</li>
		* </ul>
		*
		*/

        private BaseType newBaseType(XmlElement e)
        {
            if (_Debug)
                Console.Out.WriteLine("Getting new BaseType() from: " + e);

            // What's the Element Name? This IS the OPeNDAP typename.
            String type = e.Name;

            // What is the name of this variable? Since BaseType derived types
            // are not required to have names we have to do this carefully.
            String name = null;
            XmlAttribute nameAttr = e.Attributes["name"];

            if (nameAttr != null)
                name = nameAttr.Value;

            if (_Debug)
                Console.Out.WriteLine("    type: " + type + "   name: '" + name + "'");

            // GO get a fresh new OPeNDAP variable (BaseType derived type)
            currentBT = newBaseTypeFactory(type, name);

            //Parse any Attribute tagged child elements for this variable.
            parseAttributes(e, "--- ");

            //Parse any Alias tagged child elements for this variable.
            parseAliases(e, "+++ ");

            return (currentBT);
        }


        /// <summary> The name of this method might be a bit misleading. This method is basically
        /// a wrapper for the BaseTypeFactory associated with the DDS that we are building.
        /// <p/>
        /// I think it should really be a method of BaseTypeFactory.
        /// <p/>
        /// Something like:
        /// <p/>
        /// BaseTypeFactory.getNewVariable(String typeString, String name)
        /// <p/>
        /// But, well BaseTypeFactory is an interface, so that's a crappy idea. *sigh*
        /// </summary>
        private BaseType newBaseTypeFactory(String typeString, String name)
        {
            BaseType bt;

            if (typeString.Equals("Array") || typeString.Equals("Map"))
            {
                bt = factory.newDArray();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Grid"))
            {
                bt = factory.newDGrid();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Structure"))
            {
                bt = factory.newDStructure();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Sequence"))
            {
                bt = factory.newDSequence();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Int16"))
            {
                bt = factory.newDInt16();
                bt.ClearName = name;
            }
            else if (typeString.Equals("UInt16"))
            {
                bt = factory.newDUInt16();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Int32"))
            {
                bt = factory.newDInt32();
                bt.ClearName = name;
            }
            else if (typeString.Equals("UInt32"))
            {
                bt = factory.newDUInt32();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Float32"))
            {
                bt = factory.newDFloat32();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Float64"))
            {
                bt = factory.newDFloat64();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Byte"))
            {
                bt = factory.newDByte();
                bt.ClearName = name;
            }
            else if (typeString.Equals("String"))
            {
                bt = factory.newDString();
                bt.ClearName = name;
            }
            else if (typeString.Equals("Url"))
            {
                bt = factory.newDURL();
                bt.ClearName = name;
            }
            else
                throw new NoSuchTypeException("Unknown Type: " + typeString);

            return bt;
        }


        /// <summary> Parse the Attribute tags for a given variable element in the XML document.
        /// Build the appropriate Attributes and AttributeTables and add them to the
        /// the current variable's (currentBT's) AttributeTable.
        /// </summary>
        private void parseAttributes(XmlElement e, String indent)
        {
            parseLevel++;

            String subIndent = indent + "    ";
            if (_Debug)
                Console.Out.WriteLine(indent + "Parsing Attributes: ");

            if (_Debug)
                Console.Out.WriteLine(subIndent + "currentBT: " + currentBT.TypeName + " " + currentBT.ClearName);

            // Get all of the Attribute elements (tagged <Attribute>)
            IEnumerator attrElements = e.SelectNodes("Attribute", opendapNameSpace).GetEnumerator();


            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            while (attrElements.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                XmlElement attrElement = (XmlElement) attrElements.Current;

                String name = null;
                XmlAttribute nameAttr = attrElement.Attributes["name"];
                // no need to check that the GetAttribute call worked because the Schema enforces
                // the presence of the "name" attribute for the <Attribute> tag in the OPeNDAP namespace
                name = nameAttr.Value;

                String typeName = null;
                XmlAttribute typeAttr = attrElement.Attributes["type"];
                // no need to check that the GetAttribute call worked because the Schema enforces
                // the presence of the "type" attribute for the <Attribute> tag in the OPeNDAP namespace
                typeName = typeAttr.Value;

                // Is this Attribute a container??
                if (typeName.Equals("Container"))
                {
                    // Make sure that the document is valid for Attribute Containers and Values
                    IEnumerator valueChildren = attrElement.SelectNodes("value", opendapNameSpace).GetEnumerator();
                    //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                    if (valueChildren.MoveNext())
                        throw new AttributeBadValueException("Container Attributes may " +
                                                             "contain only other Attributes.\n" +
                                                             "Container Attributes may NOT " + "contain values.");

                    // Cache the currentAT (AttributeTable), this might be a null
                    // in which case the the container should be added to the currentBT.
                    AttributeTable cacheAttributeTable = currentAT;

                    if (_Debug)
                        Console.Out.WriteLine(indent + "currentBT: " + currentBT.TypeName + " " + currentBT.ClearName);
                    if (_Debug)
                        Console.Out.WriteLine(indent + "Attribute '" + name + "' is type " + typeName);

                    // Add the Attribute container to the appropriate object.
                    // If the currentAT is null, this indicates that we are working
                    // on the top level attributes for the currentBT, if it's not
                    // then we are working on the Attributes for some AttributeTable
                    // contained within the top level Attributes in the currentBT.
                    // Set the currentAT to the newly built (and returned) AttributeTable
                    if (currentAT == null)
                        currentAT = currentBT.appendAttributeContainer(name);
                    else
                        currentAT = currentAT.appendContainer(name);

                    // Go parse the child Attributes of this Attribute table.
                    // Note that this is a recursive call.
                    parseAttributes(attrElement, indent + "    ");

                    // Now parse all of the Aliases that exist in this Attribute table.
                    parseAliases(attrElement, "+++ ");

                    // restore the currentAT from the cached one, thus regaining the
                    // the state that we entered this method with.
                    currentAT = cacheAttributeTable;
                }
                else
                {
                    // Make sure that the document is valid for Attribute Containers and Values
                    IEnumerator attrChildren = attrElement.SelectNodes("Attribute", opendapNameSpace).GetEnumerator();
                    //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                    if (attrChildren.MoveNext())
                        throw new AttributeBadValueException("Attributes must be of type Container " +
                                                             "in order to contain other Attributes.\n" +
                                                             "Attributes of types other than Container " +
                                                             "must contain values.");

                    // Get the  <value> elements
                    IEnumerator valueChildren = attrElement.SelectNodes("value", opendapNameSpace).GetEnumerator();

                    // Walk through the <value> elements
                    //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
                    while (valueChildren.MoveNext())
                    {
                        //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                        XmlElement valueChild = (XmlElement) valueChildren.Current;

                        // Get the content of the value.
                        // There are several methods for getting this content in the
                        // org.jdom.Element object. The method getText() makes no effort
                        // to "normalize" the white space content. IE tabs, spaces,
                        // carriage return, newlines are all preserved. This might not
                        // be the right thing to do, but only time will tell.
                        String value_Renamed = valueChild.OuterXml;

                        if (_Debug)
                        {
                            Console.Out.WriteLine(subIndent + "Attribute '" + name + "' of " + currentBT.ClearName +
                                                  " is type " + typeName + " and has value: " + value_Renamed);
                        }

                        // get the Attribute value type code.
                        int typeVal = Attribute.getTypeVal(typeName);

                        // Add the attribute and it's value to the appropriat
                        // container. Note that the interface for appending
                        // values to NDODS.DAP.Attributes is built such that
                        // the Attribute must be named each time. If the Attribte
                        // name already exists, then the value is added to the list
                        // of values for the Attribute. If the Attribute name does not
                        // already exist, a new Attribute is made to hold the value.
                        if (currentAT == null)
                            currentBT.AppendAttribute(name, typeVal, value_Renamed, true);
                        else
                            currentAT.appendAttribute(name, typeVal, value_Renamed, true);
                    }
                }
            }


            parseLevel--;
        }


        /// <summary> Parse all of the Alias tags in this element of the XML document.
        /// Add each one to the correct Attribute Table.
        /// </summary>
        private void parseAliases(XmlElement e, String indent)
        {
            parseLevel++;

            String subIndent = indent + "    ";
            if (_Debug)
                Console.Out.WriteLine(indent + "Parsing Aliases: ");

            if (_Debug)
                Console.Out.WriteLine(subIndent + "currentBT: " + currentBT.TypeName + " " + currentBT.ClearName);

            // Get the Alias elements
            IEnumerator aliasElements = e.SelectNodes("Alias", opendapNameSpace).GetEnumerator();


            //UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
            while (aliasElements.MoveNext())
            {
                //UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
                XmlElement aliasElement = (XmlElement) aliasElements.Current;

                String name = null;
                XmlAttribute nameAttr = aliasElement.Attributes["name"];
                // no need to check that the GetAttribute call worked because the Schema enforces
                // the presence of the "name" attribute for the <Alias> tag in the OPeNDAP namespace
                name = nameAttr.Value;

                String attributeName = null;
                XmlAttribute attributeAttr = aliasElement.Attributes["Attribute"];
                // no need to check that the GetAttribute call worked because the Schema enforces
                // the presence of the "Attribute" attribute for the <Alias> tag in the OPeNDAP namespace
                attributeName = attributeAttr.Value;

                if (_Debug)
                {
                    Console.Out.WriteLine(subIndent + "The name '" + name + "' is aliased to dds attribute: '" +
                                          attributeName + "'");
                }

                // Add the Alias to the appropriate container.
                if (currentAT == null)
                    currentBT.addAttributeAlias(name, attributeName);
                else
                    currentAT.addAlias(name, attributeName);
            }

            parseLevel--;
        }


        /// <summary> This method is used to normalize strings prior
        /// to their inclusion in XML documents. XML has certain parsing requirements
        /// around reserved characters. These reserved characters must be replaced with
        /// symbols recognized by the XML parser as place holder for the actual symbol.
        /// <p/>
        /// The rule for this normalization is as follows:
        /// <p/>
        /// <ul>
        /// <li> The &lt; (less than) character is replaced with &amp;lt;
        /// <li> The &gt; (greater than) character is replaced with &amp;gt;
        /// <li> The &amp; (ampersand) character is replaced with &amp;amp;
        /// <li> The ' (apostrophe) character is replaced with &amp;apos;
        /// <li> The &quot; (double quote) character is replaced with &amp;quot;
        /// </ul>
        /// 
        /// </summary>
        /// <param name="s">The String to be normalized.
        /// </param>
        /// <returns> The normalized String.
        /// </returns>
        public static String normalizeToXML(String s)
        {
            // Some handy definitons.
            String xmlGT = "&gt;";
            String xmlLT = "&lt;";
            String xmlAmp = "&amp;";
            String xmlApos = "&apos;";
            String xmlQuote = "&quot;";

            bool Debug = false;
            StringBuilder sb = new StringBuilder(s);

            for (int offset = 0; offset < sb.Length; offset++)
            {
                char c = sb[offset];

                switch (c)
                {
                    case '>': // GreaterThan
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlGT, offset, offset + 1 - offset);
                        break;


                    case '<': // Less Than
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlLT, offset, offset + 1 - offset);
                        break;


                    case '&': // Ampersand
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlAmp, offset, offset + 1 - offset);
                        break;


                    case '\'': // Single Quote
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlApos, offset, offset + 1 - offset);
                        break;


                    case '\"': // Double Quote
                        sb.Replace(sb.ToString(offset, offset + 1 - offset), xmlQuote, offset, offset + 1 - offset);
                        break;


                    default:
                        break;
                }
            }
            if (Debug)
                Console.Out.WriteLine("String: `" + s + "` normalized to: `" + sb + "`");

            return (sb.ToString());
        }
    }
}